package com.winit.schedule.job.tracker.web.controller;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.winit.schedule.core.repository.constants.LogType;
import com.winit.schedule.core.repository.entity.*;
import com.winit.schedule.core.repository.query.*;
import com.winit.schedule.core.task.TaskInterfaceType;
import com.winit.schedule.core.zk.ZKService;
import com.winit.schedule.job.tracker.util.Md5Encrypt;
import com.winit.schedule.job.tracker.util.ZentaoUtils;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.winit.schedule.core.conf.Configuration;
import com.winit.schedule.core.model.JobID;
import com.winit.schedule.core.repository.constants.TaskItemStatus;
import com.winit.schedule.core.repository.constants.TaskStatus;
import com.winit.schedule.core.utils.Clock;
import com.winit.schedule.core.utils.CronExpression;
import com.winit.schedule.core.utils.StringUtils;
import com.winit.schedule.job.tracker.JobTrackerContext;
import com.winit.schedule.job.tracker.conf.JobConfiguration;
import com.winit.schedule.job.tracker.monitor.ZKServiceMonitor;
import com.winit.schedule.job.tracker.repository.ConsoleDataManager;
import com.winit.schedule.job.tracker.util.Constants;
import com.winit.schedule.job.tracker.web.auth.User;
import com.winit.schedule.job.tracker.web.vo.JobTrackerNode;
import com.winit.schedule.webc.annotation.Controller;
import com.winit.schedule.webc.annotation.RequestMapping;
import com.winit.schedule.webc.annotation.ResponseType;

@Controller
public class JobController extends BaseController{

    private static Logger        logger   = LoggerFactory.getLogger(JobController.class);

    private static JobController instance = null;

    private ConsoleDataManager   consoleDataManager;

    private JobTrackerContext    jobTrackerContext;

    private JobController(JobTrackerContext jobTrackerContext){
        this.jobTrackerContext = jobTrackerContext;
        this.consoleDataManager = jobTrackerContext.getConsoleDataManager();
    }

    public static synchronized JobController getInstance(JobTrackerContext context) {
        if (null == instance) {
            instance = new JobController(context);
        }
        return instance;
    }

    @RequestMapping("queryTaskList")
    public List<TaskEntity> queryTaskEntitys(HttpServletRequest req, HttpServletResponse resp) {
        String queryStr = req.getParameter("taskQuery");
        TaskQuery query = JSON.parseObject(queryStr, TaskQuery.class);
        if (null == query) {
            query = new TaskQuery();
        }
        List<TaskEntity> entitys = consoleDataManager.findTaskEntity(query);
        return entitys;
    }

    @RequestMapping("queryTaskRunDetail")
    public Map<String, Object> queryTaskRunDetails(HttpServletRequest req, HttpServletResponse resp) {

        String taskId = req.getParameter("taskId");
        Configuration conf = jobTrackerContext.getConf();
        long timeout = conf.getLong(JobConfiguration.HEARTBEAT_TIMEOUT_MS,
            JobConfiguration.DEFAULT_HEARTBEAT_TIMEOUT_MS);

        Map<String, Object> val = consoleDataManager.queryTaskRunDetails(taskId);

        val.put("timeout", Clock.getSystemTime() - timeout);

        return val;
    }

    @RequestMapping("queryAllTaskTracker")
    public List<TaskTrackerEntity> queryAllTaskTrackers(HttpServletRequest req, HttpServletResponse resp) {
        TaskTrackerQuery query = new TaskTrackerQuery();
        return consoleDataManager.queryTaskTrackers(query);
    }

    @RequestMapping("queryTaskById")
    public TaskEntity getTaskById(HttpServletRequest req, HttpServletResponse resp) {
        String taskId = req.getParameter("taskId");
        return consoleDataManager.getTaskById(taskId);
    }

    @RequestMapping("saveTask")
    public Object saveTask(HttpServletRequest req, HttpServletResponse resp) {
        String taskStr = req.getParameter("task");
        TaskEntity task = JSON.parseObject(taskStr, TaskEntity.class);
        String reMsg = consoleDataManager.saveTask(task);
        return buildMesssage(reMsg);
    }

    @RequestMapping("stopTask")
    public Object stopTask(HttpServletRequest req, HttpServletResponse resp) {
        String taskId = req.getParameter("taskId");
        String reMsg = "";
        try {
            consoleDataManager.updateTaskStatus(taskId, TaskStatus.STOP);
        } catch (Exception e) {
            logger.error("Stop task failure.", e);
            reMsg = "Stop task failure.";
        }
        return buildMesssage(reMsg);
    }

    @RequestMapping("startTask")
    public Object startTask(HttpServletRequest req, HttpServletResponse resp) {
        String taskId = req.getParameter("taskId");
        long size = consoleDataManager.findRunningTaskItemNumByTaskId(taskId);
        String reMsg = "";
        if (size > 0) {
            reMsg = "Please wait for all taskitem stop";
        } else {
            try {
                consoleDataManager.updateTaskStatus(taskId, TaskStatus.START);
            } catch (Exception e) {
                logger.error("Start task failure.", e);
                return "Start task failure.";
            }
            reMsg = "";
        }

        return buildMesssage(reMsg);
    }

    @RequestMapping("queryAllJobs")
    public List<JobEntity> queryAllJob(HttpServletRequest req, HttpServletResponse resp) {
        String queryStr = req.getParameter("jobQuery");
        JobQuery query = JSON.parseObject(queryStr, JobQuery.class);
        return consoleDataManager.queryAllJob(query);
    }

    @RequestMapping("saveJob")
    public Object saveJob(HttpServletRequest req, HttpServletResponse resp) {
        String reMsg = "";

        boolean valid = true;
        try {
            String jobStr = req.getParameter("job");
            String taskIdstr = req.getParameter("taskIds");
            String[] ids = taskIdstr.split(",");
            JobEntity job = JSON.parseObject(jobStr, JobEntity.class);

            if (null != job.getRunStartTime() && job.getRunStartTime().trim().length() > 0) {
                String errMsg = validCornExpression(job.getRunStartTime());
                if (!StringUtils.isBlank(errMsg)) {
                    valid = false;
                    reMsg = "启动时间设置错误：" + errMsg;
                }
            }

            if (null != job.getRunEndTime() && job.getRunEndTime().trim().length() > 0) {
                String errMsg = validCornExpression(job.getRunEndTime());
                if (!StringUtils.isBlank(errMsg)) {
                    valid = false;
                    reMsg = "结束时间设置错误：" + errMsg;
                }
            }

            if (valid)  {
                if (null != ids && ids.length > 0) {
                    for (String taskId : ids) {
                        boolean hasTaskItem = consoleDataManager.hasTaskItem(taskId);
                        if (!hasTaskItem) {
                            TaskEntity task = consoleDataManager.getTaskById(taskId);
                            if (null == task) {
                                valid = false;
                                reMsg = "任务【" + taskId + "】不存在或者已删除。";
                                break;
                            }

                            if (TaskInterfaceType.SCHEDULE_TASK_SINGLE.equals(task.getInterfaceType())) {
                                consoleDataManager.saveTask(task);
                            } else {
                                valid = false;
                                reMsg = "任务【" + taskId + "】未配置运行参数，请去任务管理界面编辑任务参数。";
                                break;
                            }
                        }
                    }
                } else {
                    valid = false;
                    reMsg = "保存失败，未选择任务。";
                }
            }

            if (valid) {
                consoleDataManager.saveJob(job, Arrays.asList(ids));
            }

        } catch (Exception e) {
            logger.error("Save job failure.", e);
            reMsg = "Save job failure.";
        }

        return buildMesssage(reMsg);
    }

    @RequestMapping("startJob")
    public Object startJob(HttpServletRequest req, HttpServletResponse resp) {
        String reMsg = "";
        try {
            String jobId = req.getParameter("jobId");
            JobID jobID = JSON.parseObject(jobId, JobID.class);
            //if (!existsRunningTask(jobID)) {
                consoleDataManager.startJob(jobID);
//            } else {
//                reMsg = "Please wait for all the tasks to run to the end";
//            }
        } catch (Exception e) {
            logger.error("Start job failure.", e);
            reMsg = "Start job failure.";
        }
        return buildMesssage(reMsg);
    }

    @RequestMapping("stopJob")
    public Object stopJob(HttpServletRequest req, HttpServletResponse resp) {
        String reMsg = "";
        try {
            String jobId = req.getParameter("jobId");
            JobID jobID = JSON.parseObject(jobId, JobID.class);
            consoleDataManager.stopJob(jobID);
        } catch (Exception e) {
            logger.error("Stop job failure.", e);
            reMsg = "Stop job failure.";
        }
        return buildMesssage(reMsg);
    }

    @RequestMapping("delJob")
    public Object delJob(HttpServletRequest req, HttpServletResponse resp) {
        String reMsg = "";
        try {
            String jobId = req.getParameter("jobId");
            JobID jobID = JSON.parseObject(jobId, JobID.class);
            consoleDataManager.delJob(jobID);
        } catch (Exception e) {
            logger.error("Stop job failure.", e);
            reMsg = "Stop job failure.";
        }
        return buildMesssage(reMsg);
    }

    @RequestMapping("queryJobById")
    public JobEntity queryJobById(HttpServletRequest req, HttpServletResponse resp) {
        String jobId = req.getParameter("jobId");
        return consoleDataManager.queryJobById(jobId);
    }

    @RequestMapping("queryAllUnretadTasks")
    public List<TaskEntity> queryTasksNotRelatedJob(HttpServletRequest req, HttpServletResponse resp) {
        return consoleDataManager.queryTasksNotRelatedJob();
    }

    @RequestMapping("queryAllretadTasks")
    public List<TaskEntity> queryTasksRelatedJob(HttpServletRequest req, HttpServletResponse resp) {
        String jobId = req.getParameter("jobId");
        JobID jobID = JSON.parseObject(jobId, JobID.class);
        return consoleDataManager.queryTasksRelatedJob(jobID);
    }

    private boolean existsRunningTask(JobID jobId) {
        long count = consoleDataManager.findRunningTaskItemNumByJobId(jobId);
        if (count > 0) {
            return true;
        } else {
            return false;
        }
    }

    @RequestMapping("queryTaskTrackerLog")
    public Page queryTaskTrackerLog(HttpServletRequest req, HttpServletResponse resp) {

        int page = Integer.parseInt(req.getParameter("page"));
        int rows = Integer.parseInt(req.getParameter("rows"));
        long startTime = 0;
        long endTime = 0;

        return consoleDataManager.queryTaskTrackerLog(null, startTime, endTime, page, rows);
    }

    @RequestMapping("queryJobLog")
    public Page queryJobLog(HttpServletRequest req, HttpServletResponse resp) {

        int page = Integer.parseInt(req.getParameter("page"));
        int rows = Integer.parseInt(req.getParameter("rows"));
        String logQuery = req.getParameter("logQuery");
        UserLogQuery query = JSON.parseObject(logQuery, UserLogQuery.class);

        return consoleDataManager.queryJobLog(query, page, rows);
    }

    @RequestMapping("queryTaskTrackerDisconnectLog")
    public Page queryTaskTrackerDisconnectLog(HttpServletRequest req, HttpServletResponse resp) {
        int page = Integer.parseInt(req.getParameter("page"));
        int rows = Integer.parseInt(req.getParameter("rows"));
        long startTime = 0;
        long endTime = 0;
        return consoleDataManager.queryTaskTrackerDisconnectLog(null, startTime, endTime, page, rows);
    }

    @RequestMapping("queryTaskExeFailureLog")
    public Page queryTaskExeFailureLog(HttpServletRequest req, HttpServletResponse resp) {
        int page = Integer.parseInt(req.getParameter("page"));
        int rows = Integer.parseInt(req.getParameter("rows"));

        long startTime = 0;
        long endTime = 0;
        return consoleDataManager.queryTaskExeFailureLog(null, null, startTime, endTime, page, rows);
    }

    @RequestMapping("queryTaskLog")
    public Page queryTaskLog(HttpServletRequest req, HttpServletResponse resp) {
        int page = Integer.parseInt(req.getParameter("page"));
        int rows = Integer.parseInt(req.getParameter("rows"));
        String logQuery = req.getParameter("logQuery");
        LogQuery query = JSON.parseObject(logQuery, LogQuery.class);
        query.setOffset(page);
        query.setLimit(rows);
        return consoleDataManager.queryTaskLog(query);
    }

    @RequestMapping("queryTaskRunRecord")
    public Page queryTaskRunRecord(HttpServletRequest req, HttpServletResponse resp) {
        int page = Integer.parseInt(req.getParameter("page"));
        int rows = Integer.parseInt(req.getParameter("rows"));
        String jobId = req.getParameter("jobId");
        String taskId = req.getParameter("taskId");
        long startTime = 0;
        long endTime = 0;
        return consoleDataManager.queryTaskRunRecord(jobId, taskId, startTime, endTime, page, rows);
    }

    /**
     * 验证时间表达式
     * 
     * @param expression
     * @return 返回错误信息
     */
    private String validCornExpression(String expression) {
        CronExpression cron;
        try {
            cron = new CronExpression(expression);
            Date date = cron.getNextValidTimeAfter(Clock.getCurrentDate());
            if (date == null) {
                return "Unable to get the next execution time";
            }
        } catch (ParseException e) {
            return e.getMessage();
        }
        return null;
    }

    @RequestMapping(value = "login", responseTyte = ResponseType.FORWARD)
    public String login(HttpServletRequest req, HttpServletResponse resp) {
        String userName = req.getParameter("username");
        String pwd = req.getParameter("password");
        String gotoPage = "index.jsp";
        UserEntity user = null;
        if (!StringUtils.isBlank(userName) && !StringUtils.isBlank(pwd)) {

            user = consoleDataManager.findByUsernameAndPassword(userName, Md5Encrypt.md5(pwd));
            if (null == user && !"admin".equalsIgnoreCase(userName.trim())) {
                user = consoleDataManager.queryUserByUsername(userName);
                if (null != user && !user.isDisabled()) {
                    String uri = jobTrackerContext.getConf().getString(JobConfiguration.ZENTAO_LOGIN_URI, JobConfiguration.DEFAULT_ZENTAO_LOGINURI);
                    UserEntity zenUser = ZentaoUtils.login(userName, pwd, uri);
                    if (null != zenUser) {
                        consoleDataManager.updateUserPws(user.getUsername(), Md5Encrypt.md5(pwd));
                    } else {
                        user = null;
                    }
                }
            }

            if (null != user) {
                if (!user.isDisabled()) {
                    consoleDataManager.logger(user, LogType.LOGIN);
                    User sessionUser = new User();
                    sessionUser.setUsername(user.getUsername());
                    sessionUser.setAdmin(user.isAdmin());
                    req.getSession().setAttribute(Constants.SESSION_USERINFO, sessionUser);
                    gotoPage = "home.jsp";
                } else {
                    req.setAttribute("errorMsg", "用户帐号被禁止登录.");
                }
            } else {
                req.setAttribute("errorMsg", "用户名或者密码错误.");
            }
        }

        return gotoPage;

    }

    @RequestMapping(value = "logout", responseTyte = ResponseType.FORWARD)
    public String logout(HttpServletRequest req, HttpServletResponse resp) {

        User user = (User) req.getSession().getAttribute(Constants.SESSION_USERINFO);
        if (null != user) {
            consoleDataManager.logger(user, LogType.LOGOUT);
        }
        req.getSession().removeAttribute(Constants.SESSION_USERINFO);
        return "index.jsp";
    }

    @RequestMapping("queryStatusExceptionTaskItem")
    public List<TaskItemEntity> queryTaskItemForStatusException(HttpServletRequest req, HttpServletResponse resp) {
        String queryStr = req.getParameter("taskItemQuery");
        TaskItemQuery query = JSON.parseObject(queryStr, TaskItemQuery.class);
        Configuration conf = jobTrackerContext.getConf();
        long timeout = conf.getLong(JobConfiguration.HEARTBEAT_TIMEOUT_MS,
            JobConfiguration.DEFAULT_HEARTBEAT_TIMEOUT_MS);
        if (null == query) {
            query = new TaskItemQuery();
        }
        query.setTimeout(Clock.getSystemTime() - timeout);
        query.setTaskItemStatusList(TaskItemStatus.RUN_STATUS);
        return consoleDataManager.queryTaskItemsByTimeout(query);
    }

    @RequestMapping("recoveryTaskItem")
    public Object recoveryTaskItem(HttpServletRequest req, HttpServletResponse resp) {
        Configuration conf = jobTrackerContext.getConf();
        long timeout = conf.getLong(JobConfiguration.HEARTBEAT_TIMEOUT_MS,
            JobConfiguration.DEFAULT_HEARTBEAT_TIMEOUT_MS);
        timeout = Clock.getSystemTime() - timeout;
        String reMsg = "";
        try {
            String id = req.getParameter("id");
            String[] ids = StringUtils.getTrimmedStrings(id);
            if (!StringUtils.isBlank(id) && null != ids && ids.length > 0) {
                consoleDataManager.recoveryTaskItemByIds(timeout, ids);
            }

        } catch (Exception e) {
            logger.error("Recovery Taskitem failure.", e);
            reMsg = "Recovery Taskitem failure.";
        }
        return buildMesssage(reMsg);
    }

    @RequestMapping("queryTaskItems4status")
    public List<TaskItemEntity> queryTaskItems4status(HttpServletRequest req, HttpServletResponse resp) {
        String time = req.getParameter("longTime"); // 单位：分钟
        long longTime = 0;
        if (!StringUtils.isBlank(time)) {
            longTime = Long.valueOf(time);
        }
        if (longTime < 0) {
            longTime = 0;
        }
        String queryStr = req.getParameter("taskItemQuery");
        TaskItemQuery query = JSON.parseObject(queryStr, TaskItemQuery.class);
        query.setLongTime(longTime * 60 * 1000);
        return consoleDataManager.queryTaskItemsForLongtime(query);
    }

    @RequestMapping("queryJobtrackerNodes")
    public List<JobTrackerNode> queryJobtrackerNodes(HttpServletRequest req, HttpServletResponse resp) {
        List<JobTrackerNode> nodes = new ArrayList<JobTrackerNode>();
        try {
            ZKServiceMonitor zkService = this.jobTrackerContext.getZkService();
            ZooKeeper zk = zkService.getZooKeeper();

            List<String> childNodes = zk.getChildren(zkService.getPathServer(), false);
            JobTrackerNode jobTrackerNode = null;
            if (null != childNodes && childNodes.size() > 0) {
                for (String path : childNodes) {
                    String zkPath = zkService.getPathServer() + "/" + path;
                    byte[] data = zk.getData(zkPath, false, null);
                    jobTrackerNode = new JobTrackerNode(zkPath, new String(data, "utf-8"), 0);
                    nodes.add(jobTrackerNode);
                }
            }

        } catch (Exception e) {
            logger.error("query JobTracker Nodes failure from zookeeper.", e);
        }
        return nodes;
    }

    @RequestMapping("queryBlackList")
    public String queryBlackList(HttpServletRequest req, HttpServletResponse resp) {
        String blackList = "";
        try {
            ZKServiceMonitor zkService = this.jobTrackerContext.getZkService();
            ZooKeeper zk = zkService.getZooKeeper();

            String zkPath = zkService.getRootPath()  + "/" +  ZKService.BLACK_LIST;
            Stat stat  = zk.exists(zkPath, false);

            if (null != stat) {
                byte[] data = zk.getData(zkPath, false, null);
                if (null != data) {
                    blackList = new String(data, "utf-8");
                }
            }

        } catch (Exception e) {
            logger.error("query JobTracker Nodes failure from zookeeper.", e);
            blackList = "Try again";
        }
        return blackList;
    }
    
    @RequestMapping("clearCache")
    public Object clearCache(HttpServletRequest req, HttpServletResponse resp) {
        String taskTrackerUuid = req.getParameter("uuid");
        try {
            String taskRunningUUID = String.valueOf(taskTrackerUuid);
            synchronized (taskRunningUUID) {
                this.consoleDataManager.removeTaskTrackerAndTaskDefines(taskTrackerUuid);
            }
            return buildMesssage(null);
        } catch (Exception e) {
            return buildMesssage("clear cache failure.");
        }
    }
}
